#include "SurfaceRevolution.h"

#include <glm\gtc\matrix_transform.hpp>


SurfaceRevolution::SurfaceRevolution(std::unique_ptr<Algoritmo1D> ptrAlg) : 
	m_generadorPts( std::move(ptrAlg) ),
	m_helper( m_generadorPts->getLimiteInferior(), m_generadorPts->getLimiteSuperior() )
{
}


SurfaceRevolution::~SurfaceRevolution() { }


void SurfaceRevolution::init( std::vector<glm::vec3> ptsControl, glm::vec3 ejeRevolucion ) {
	if (glm::length(ejeRevolucion) < 0.00001f) throw std::invalid_argument("El eje de revolucion no puede ser cero o es muy chico");

	this->m_generadorPts->init( ptsControl );
	this->m_ejeRevolucion = glm::normalize( glm::dvec3(ejeRevolucion) );	// Que quede bonito
}


void SurfaceRevolution::build(unsigned int divAngular, unsigned int cantDivisionesCurva) {
	// Se calculan los vertices de la superficie y los indices para que la superfice quede cerrada
	std::vector<glm::vec3> vecVertices = buildVertices(divAngular, cantDivisionesCurva);

	// Existe una "fila extra" de vertices para que la superficie sea cerrada, por eso no es igual a "cantDivisionesCurva"
	const unsigned int cantCurvas = vecVertices.size() / cantDivisionesCurva;
	std::vector<unsigned int> vecIndices = this->m_helper.buildIndexBuffer(cantDivisionesCurva, cantCurvas);
	std::vector<glm::vec3> vecNormales = this->m_helper.buildNormales(cantDivisionesCurva, vecVertices);
	std::vector<glm::vec2> vecCoords = this->m_helper.buildCoordsTextura(cantDivisionesCurva, cantCurvas);

	// Guarda los resultados
	this->m_vertices.swap( vecVertices );
	this->m_indexBuffer.swap( vecIndices );
	this->m_normales.swap( vecNormales );
	this->m_coordsTextura.swap( vecCoords );
}


std::vector<glm::vec3> SurfaceRevolution::buildVertices(unsigned int divAngular, unsigned int cantDivisionesCurva) const {
	// Los vertices de la curva que se rota
	std::vector<glm::vec3> vertCurva = buildCurva(cantDivisionesCurva);

	// Luego se rota la curva sobre el eje
	std::vector<glm::vec3> vertRotados;
	const double pasoRotacion = (2.0 * 3.14159) / divAngular;

	for (unsigned int i = 0; i < divAngular; ++i){
		// Solo me interesa la rotacion, por eso descarto la parte de la matriz que tiene que ver con la translacion
		const double rotacion = glm::degrees( i * pasoRotacion );
		glm::dmat3 matRotacion = glm::dmat3( glm::rotate( glm::dmat4(1.0), rotacion, this->m_ejeRevolucion ) );

		// Se rotan los puntos de a uno
		for (auto it = vertCurva.cbegin(); it != vertCurva.cend(); ++it){
			glm::vec3 ptRotado = glm::vec3( matRotacion * glm::dvec3(*it) );
			vertRotados.push_back( ptRotado );
		}
	}

	/**	Todos los puntos ya fueron rotados armando la malla de vertices de la superficie
	 *	PERO! para que todo quede bien bonito se agrega al final una copia de los 
	 *	vertices del inicio, de esta forma se puede armar una superficie cerrada sin tener problemas
	 *	con, por ejemplo, las coordenadas de textura.
	 *	Una ventaja de esto es que simplifica el codigo de los indices
	 * */
	vertRotados.insert( vertRotados.end(), vertCurva.begin(), vertCurva.end() );

	return vertRotados;
}


std::vector<glm::vec3> SurfaceRevolution::buildCurva(unsigned int cantDivisionesCurva) const {
	// Calculos previos
	const float limInferior = this->m_generadorPts->getLimiteInferior();
	const float limSuperior = this->m_generadorPts->getLimiteSuperior();
	const float tamPaso = (limSuperior - limInferior) / (cantDivisionesCurva -1);

	// Los puntos de la curva
	std::vector<glm::vec3> ptsCurva;
	for (unsigned int i = 0; i < cantDivisionesCurva; ++i){
		const float u = limInferior + i * tamPaso;

		glm::vec3 pt = this->m_generadorPts->eval( u );
		ptsCurva.push_back( pt );
	}

	return ptsCurva;
}


std::vector<glm::vec3> SurfaceRevolution::getVertices() const {
	return this->m_vertices;
}


std::vector<unsigned int> SurfaceRevolution::getIndexBuffer() const {
	return this->m_indexBuffer;
}


std::vector<glm::vec3> SurfaceRevolution::getNormales() const {
	return this->m_normales;
}


std::vector<glm::vec2> SurfaceRevolution::getCoordsTextura() const {
	return this->m_coordsTextura;
}

